package com.dayu.gulimall.product.web;

import com.dayu.gulimall.product.entity.CategoryEntity;
import com.dayu.gulimall.product.service.CategoryService;
import com.dayu.gulimall.product.vo.Catalog2Vo;
import com.sun.corba.se.impl.orbutil.LogKeywords;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author dayu
 * @version 1.0
 * @email dayu007@foxmail.com
 * @date 2020/12/8 21:24
 * @description
 */
@Controller
public class IndexController {

    @Autowired
    private CategoryService categoryService;

    @Autowired
    RedissonClient redissonClient;
    @Autowired
    StringRedisTemplate redisTemplate;

    @GetMapping({"/", "/index.html"})
    public String index(Model model) {
        List<CategoryEntity> categoryEntities = categoryService.getLevel1Category();
        model.addAttribute("categoryList", categoryEntities);
        //试图解析器拼串：classpath:/templates/ +path
        return "index";
    }

    @ResponseBody
    @GetMapping("index/json/catalog.json")
    public Map<String, List<Catalog2Vo>> getCatalogJson(Model model) {
        Map<String, List<Catalog2Vo>> catalogJson = categoryService.getCatalogJson();
        return catalogJson;
    }

    //保证一定能读到最新数据,修改期间，写锁是一个排他锁（互斥锁,独享锁）。读锁是一个共享锁
    // 写锁没释放读就必须等待
    //读＋读:相当于无锁，并发读，只会在redis中记录好，所有当前的读锁。他们都会同时加锁成功
    // 写+读:等待写锁释放
    //写+写:阻塞方式
    //读+写:有读锁。写也需要等待。
    // 只要有写的存在，都必须等待
    @ResponseBody
    @GetMapping("/write")
    public String write() {
        String s = UUID.randomUUID().toString();
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("read-write");
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        try {
            System.out.println("加锁成功！" + Thread.currentThread().getId());
            redisTemplate.opsForValue().set("writeValue", s);
            Thread.sleep(30000);
        } catch (Exception e) {
        } finally {
            System.out.println("释放锁。。" + Thread.currentThread().getId());
            rLock.unlock();
        }
        return s;
    }

    @ResponseBody
    @GetMapping("/read")
    public String read() {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("read-write");
        RLock rLock = readWriteLock.readLock();
        rLock.lock();
        try {
            System.out.println("获取读锁成功！" + Thread.currentThread().getId());
            return redisTemplate.opsForValue().get("writeValue");
        } catch (Exception e) {
        } finally {
            System.out.println("释放锁。。" + Thread.currentThread().getId());
            rLock.unlock();
        }
        return "";

    }

    @ResponseBody
    @GetMapping("/hello")
    public String hello() {
        RLock lock = redissonClient.getLock("hello-lock");
        //redisson获取一把锁，只要吗名称一样，都是同一把锁
        //加锁，阻塞式 等待，默认加锁的时长时30秒
        // lock.lock();

        // 1、锁的自动续期，如果 业务超长，运行期间，自动给锁续上新的时长30s。不用担心 业务时间长了导致的自动过期被删除。
        // 2、加锁的业务，只要运行完成，就不会给锁续时长，即使不主动解锁，默认也会在30s之后自动解锁
        //第二种加锁方式：
        // 20s后自动解锁，自动解锁的时间一定要大于业务执行时间
        //      - 1、如果我们传递了锁的超时时间，就会发送redis脚本，进行占锁，默认的超时时间就是指定的时间
        //      - 2、如果没有指定锁的超时时间，就使用30*1000毫秒【lockWatchdogTimeout = 30 * 1000 看门狗默认时间】
        //      -3、 只要占锁成功就会启动 一个定时任务，重新给锁指定一个超时时间，新的过期时间就是看门狗的默认 时间，每个10s(1/3看门狗时间)就会自动更新一下internalLockLeaseTime / 3
        lock.lock(20, TimeUnit.SECONDS);
        try {
            //业务执行
            System.out.println("加锁成功，执行业务。。。" + Thread.currentThread().getId());
            TimeUnit.SECONDS.sleep(30);
        } catch (InterruptedException e) {
        } finally {
            //3、 解锁。
            System.out.println("执行解锁。。。" + Thread.currentThread().getId());
            lock.unlock();
        }

        return "hello";
    }


    /**
     * 锁门原理：
     * 案例：学校管理员管理了多个管理大门，当学生走完一间房间，就锁一个门，当最后的门锁完了之后，锁学校的大门，全部放假，否则，countDownLatch 一直阻塞等待
     *
     * @return
     * @throws InterruptedException
     */
    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {
        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("door");
        countDownLatch.trySetCount(5);
        countDownLatch.await();
        return "放假了。。。";
    }

    @GetMapping("/gogo")
    @ResponseBody
    public String gogo() throws InterruptedException {
        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("door");
        //计数减一
        countDownLatch.countDown();
        long count = countDownLatch.getCount();
        System.out.println("==countDown==" + count);
        return "放假了。。。" + count;
    }


    /***
     * 信号量锁
     *        案例：车库停车
     *        3个车位
     *        缓存中放3个数据，
     *        每次获得锁，semaphore.acquire() 数据库park中少一个数
     *        开走一个车，semaphore.release() 数据库park中多一个车位
     *
     * @return
     * @throws InterruptedException
     */
    @GetMapping("/park")
    @ResponseBody
    public String RSemaphoreLock() throws InterruptedException {
        RSemaphore semaphore = redissonClient.getSemaphore("park");
        semaphore.acquire();
        return "ok";
    }

    @GetMapping("/go")
    @ResponseBody
    public String semaphoreGo()  {
        RSemaphore semaphore = redissonClient.getSemaphore("park");
        semaphore.release();
        return "ok";
    }


}
